/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.gui.security;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

import blueprint4j.db.DBConnection;
import blueprint4j.db.DBTools;
import blueprint4j.db.DataException;
import blueprint4j.db.Entity;
import blueprint4j.db.FieldBoolean;
import blueprint4j.db.FieldDateTime;
import blueprint4j.db.FieldPassword;
import blueprint4j.db.FieldRename;
import blueprint4j.db.FieldString;
import blueprint4j.db.FieldText;
import blueprint4j.db.FieldUnique;
import blueprint4j.db.TableName;
import blueprint4j.utils.BindException;
import blueprint4j.utils.Log;
import blueprint4j.utils.ThreadScheduable;
import blueprint4j.utils.ThreadSchedule;
import blueprint4j.utils.VectorString;


/**
 * This class will force a user to login, and will force a re login every 15 mins
 */
public class User extends Entity implements ThreadScheduable {

	static String VERSION = "$Id: User.java,v 1.4 2004/09/14 13:55:08 travisb Exp $";
	private static final long serialVersionUID = 1L;
	static final String TABLE_NAME = "d_users";
	public static User CURRENT_USER = null;

	private static VectorString PRIVILEGES = null;
	private static Frame owner = null;

	public TableName table_name = new TableName(TABLE_NAME, this);

	public FieldUnique id = new FieldUnique("id", 0, "The Record Identifier", this);
	public FieldString username = new FieldString("username",0,"User name",50,this);
	public FieldPassword passwd = new FieldPassword("passwd",0,"Password",50,this);
	public FieldRename rename_password = new FieldRename("password",passwd,this);
	public FieldString email = new FieldString("email",0,"Email",200,this);
	public FieldString first_name = new FieldString("first_name",0,"First Name",100,this);
	public FieldString last_name = new FieldString("last_name",0,"Last Name",100,this);
	public FieldDateTime last_login = new FieldDateTime("last_login",0,"Last Login",this);
	public FieldText privilege = new FieldText("privilege",0,"Privilege",this);
	public FieldString limit_id = new FieldString("limit_id",0,"Limit id",26,this);
	public FieldBoolean is_admin = new FieldBoolean("is_admin",0,"Is Admin",this);

	private boolean skipped_first = false;
	private boolean administrator = false;
	
	private static long last_logged_on = 0; 

	static {
		ThreadSchedule.add(new User());
	}

	public User() {
		super();
	}

	public User(String level)
	throws DataException {
		administrator = true;
		is_admin.set(new Boolean(true));
	}

	public User(DBConnection connection)
	throws DataException, SQLException {
		super(connection);
	}

	public User(DBConnection connection,String limit_id)
	throws DataException, SQLException {
		this(connection);
		this.limit_id.set(limit_id);
	}

	public Entity getNewInstance() {
		return new User();
	}

	public String getVersionString() {
		return VERSION;
	}

	protected int countUsers()
	throws SQLException {
		Statement smt = getConnection().createStatement();
		try {
			ResultSet rs = smt.executeQuery("select count(*) from d_users");
			rs.next();
			return rs.getInt(1);
		} finally {
			smt.close();
		}
	}

	public void save()
	throws BindException {
		try {
			if (!isLoaded() && new User(getConnection()).find("username = "+ Entity.asSQL(username.get()))!=null) {
				throw new BindException("USERNAME ["+username.get()+"] ALREADY EXISTS");
			}
			super.save();
		} catch (Exception exception) {
			throw new BindException(exception);
		}
	}

	public boolean isAdmin()
	throws DataException {
		return is_admin.get() != null && is_admin.get().booleanValue();
	}
	
	public boolean isLimited() 
	throws DataException {
		return limit_id.get() != null;
	}

	public static void setOwner(Frame p_owner) {
		owner = p_owner;
	}
	
	/**
	 * This should be set every time an system event occurs
	 * Will reset the user's relogin time
	 */
	public static void resetLoginTime() {
		last_logged_on = System.currentTimeMillis();
	}
	/**
	* 
	*/
	public static void login(Frame p_owner,VectorString privilege)
	throws SQLException {
		owner = p_owner;
		LoginDialog login = null;
		if (privilege != null) {
			PRIVILEGES = privilege;
		}
		// IF NO USERS EXIST THEN CREATE AN ADMINISTRATOR
		if (new User(DBTools.getDLC()).countUsers() == 0) {
			JOptionPane.showMessageDialog(owner,"Enter administrator username and passwd","NO ADMIN",JOptionPane.INFORMATION_MESSAGE);
			login = new LoginDialog(owner,DBTools.getDLC(),privilege);
		} else {
			login = new LoginDialog(owner,DBTools.getDLC(),null);
		}
		login.setVisible(true);
		if (login.getUser() == null) {
			JOptionPane.showMessageDialog(owner,"LOGIN FAILED");
			login(owner,privilege);
		} else {
			CURRENT_USER = login.getUser();
			last_logged_on = System.currentTimeMillis();
		}
	}

	public static User loadUser(DBConnection connection,String username,String password)
	throws SQLException {
		User user = (User) new User(connection).find("username = "+Entity.asSQL(username)+" and passwd = "+Entity.asSQL(password));
		if (CURRENT_USER != null && user != null && !CURRENT_USER.username.get().equals(user.username.get())) {
			throw new SQLException("CAN NOT CHANGE USER. " + CURRENT_USER.username.get() + " MUST FIRST LOG OFF");
		}
		return user;
	}

	public static User getAdmin(DBConnection connection)
	throws SQLException {
		return (User) new User(connection).find("is_admin = "+Entity.asSQL(new Boolean(true)));
	}
	
	public VectorString getPrivilege()
	throws DataException {
		if (privilege.get() != null) {
			return new VectorString(this.privilege.get(),",");
		}
		return new VectorString();
	}

	public boolean can(String privilege)
	throws DataException {
		if (getPrivilege() != null) {
			return getPrivilege().containsIgnoreCase(privilege);
		}
		return false;
	}

	public boolean canPartial(String privilege)
	throws DataException {
		if (getPrivilege() != null) {
			return getPrivilege().startsWith(privilege).size() > 0;
		}
		return false;
	}

	public void setPrivilege(VectorString privileges)
	throws SQLException,BindException {
		this.privilege.set(privileges.toString(","));
	}

	/**
	 * This displays all privileges that the current user
	 * has ownership of
	 * For example an Administrator would have ownership of all
	 * privileges except Administrator himself
	 * An admininstrator can not create another administrator
	 */
	public VectorString getSubPrivilegeDisplay()
	throws DataException {
		VectorString privilege = getPrivilege();
		if (privilege.size() > 0) {
			return getPrivilege().substring(1);
		}
		return new VectorString();
	}

	public VectorString getSubPrivilege()
	throws DataException {
		VectorString sub = getSubPrivilegeDisplay();
		VectorString sub_priv = new VectorString();
		for (int i = 0;i < sub.size();i++) {
			sub_priv.add(sub.substring(i).toString(","));
		}
		return sub_priv;
	}

	public String getSQLForSubPrivilege(Entity limit_entity)
	throws DataException {
		VectorString sub = getSubPrivilegeDisplay();
		String sql = "";
		for (int i = 0;i < sub.size();i++) {
			sql += "privilege like '"+sub.get(i) + "%' or ";
		}
		if (sql.length() > 0) {
			sql= sql.substring(0,sql.length()-" or ".length());
			if (limit_entity != null) {
				sql = "limit_id = " + Entity.asSQL(limit_entity.id.getAsString()) + " and ("+sql+")";
			}
			if (!isAdmin()) {
				if (limit_id.get() != null && limit_id.get().length() > 0) {
					sql = "limit_id = " + Entity.asSQL(limit_id.get()) + " and ("+sql+")";
				} else {
					sql = "limit_id is null and ("+sql+")";
				}
			}
			return "id = '"+CURRENT_USER.id.get()+"' or ("+sql+")";
		}
		return "1=2";
	}

	public boolean keepAlive() {
		return true;
	}

	public int sleepTime() {
		return 3000;
	}

	public void process()
	throws Exception {
		if (CURRENT_USER == null) {
			return;
		}
		if (skipped_first) {
			if (PRIVILEGES != null) {
				if (System.currentTimeMillis() - last_logged_on > 30*60*1000) {
					User.login(owner,PRIVILEGES);	
				}
			}
		} else {
			skipped_first = true;
		}
	}

	public void close() throws Exception {}

	static class LoginDialog extends JDialog implements ActionListener,KeyListener {
		private static final long serialVersionUID = 1L;
		private JButton login = null,cancel = new JButton("Cancel");
		private JTextField username = new JTextField(10);
		private JPasswordField password = new JPasswordField(10);
		private User user = null;
		private VectorString privilege = null;
		private DBConnection connection = null;

		public LoginDialog(Frame owner,DBConnection connection,VectorString privilege) {
			super(owner);
			this.connection = connection;
			this.privilege = privilege;
			if (privilege != null) {
				setTitle("CREATE ADMINISTRATOR");
				login = new JButton("Create");
			} else {
				setTitle("LOGON");
				login = new JButton("Login");
			}
			setModal(true);
			this.getContentPane().setLayout(new BorderLayout());
			JPanel edit = new JPanel();
			edit.setLayout(new GridLayout(2,2));
			JPanel enter = new JPanel();
			enter.setLayout(new BorderLayout());
			edit.add(new JLabel("Username"));
			edit.add(username);
			edit.add(new JLabel("Password"));
			edit.add(password);
			enter.add(login,BorderLayout.WEST);
			enter.add(cancel,BorderLayout.EAST);
			this.getContentPane().add(edit,BorderLayout.CENTER);
			this.getContentPane().add(enter,BorderLayout.SOUTH);
			pack();
			this.setLocationRelativeTo(null);
			login.addActionListener(this);
			cancel.addActionListener(this);
			username.addKeyListener(this);
			password.addKeyListener(this);
		}

		private void login()  {
			try {
				if (privilege != null) {
					user = new User(connection);
					user.setPrivilege(privilege);
					user.username.set(username.getText());
					user.passwd.set(password.getPassword().toString());
					user.is_admin.set(new Boolean(true));
					user.save();
				} else {
					user = User.loadUser(connection,username.getText(),password.getPassword().toString());
				}
			} catch (Exception exception) {
				JOptionPane.showMessageDialog(owner,exception.getMessage());
				Log.debug.out(exception);
			}
			setVisible(false);
		}

		public void actionPerformed(ActionEvent event) {
			if (event.getSource() == login) {
				login();
			}
			if (event.getSource() == cancel) {
				ThreadSchedule.haltBlocking();
				System.exit(0);
			}
		}

		public User getUser() {
			return user;
		}

		public void keyPressed(KeyEvent e) {
			if (e.getKeyCode() == KeyEvent.VK_ENTER) {
				login();
			}
		}

		public void keyReleased(KeyEvent e) {}

		public void keyTyped(KeyEvent e) {}

	}
}

